package fr.utbm.gi.vi51.project.agent;

import java.util.ArrayList;
import java.util.List;

import javax.vecmath.Point2d;

import org.janusproject.kernel.status.Status;

import fr.utbm.gi.vi51.framework.environment.Animat;
import fr.utbm.gi.vi51.framework.environment.Perception;
import fr.utbm.gi.vi51.project.agent.body.EnemyBody;
import fr.utbm.gi.vi51.project.agent.body.MarineBody;
import fr.utbm.gi.vi51.project.agent.body.UnitBody;
import fr.utbm.gi.vi51.project.behavior.BehaviourOutput;
import fr.utbm.gi.vi51.project.behavior.SeekBehaviour;
import fr.utbm.gi.vi51.project.behavior.WanderBehaviour;
import fr.utbm.gi.vi51.project.behavior.steering.SteeringSeekBehaviour;
import fr.utbm.gi.vi51.project.behavior.steering.SteeringWanderBehaviour;
import fr.utbm.gi.vi51.project.environment.EnvironmentObject;

public abstract class Enemy extends Animat<EnemyBody> {

	private static final long serialVersionUID = 8421209612892552968L;

	// Behaviors
	protected final SeekBehaviour<?> seekBehaviour;
	protected final WanderBehaviour<?> wanderBehaviour;

	// Attack
	protected int cooldown = 0;

	public Enemy() {
		this.seekBehaviour = new SteeringSeekBehaviour();
		this.wanderBehaviour = new SteeringWanderBehaviour();
	}

	@Override
	public Status live() {
		if (((UnitBody) getBody()).getHealth() <= 0) {
			this.killMe();
		} else {
			// Enemy is wandering by default and attack when see a marine
			List<Perception> perceivedObjects = getPerceivedObjects();
			// List of perceived obstacles and enemies
			List<Point2d> perceivedObstaclesPositions = new ArrayList<>();
			List<UnitBody> perceivedMarines = new ArrayList<>();
			for (Perception p : perceivedObjects) {
				if (p.getPerceivedObject() instanceof EnvironmentObject) {
					perceivedObstaclesPositions.add(p.getPerceivedObject().getPosition());
				} else if (p.getPerceivedObject() instanceof MarineBody) {
					perceivedMarines.add((UnitBody) p.getPerceivedObject());
				}
			}
			if (perceivedMarines.size() > 0) { // if the enemy see a marine
				// it pursue/seek and attack (which is a shoot with a really close
				// attackradius frustrum)
				UnitBody nearestMarine = getNearestMarine(perceivedMarines);
				if (getBody().getFrustrum().isAttackable(getPosition(), nearestMarine.getPosition())) {
					if (this.cooldown == 0) {
						attack(nearestMarine);
					}
				} else {
					BehaviourOutput behaviour = this.seekBehaviour.runSeek(getPosition(), getDirection(),
							getCurrentLinearSpeed(), getMaxLinearSpeed(), nearestMarine.getPosition(),
							perceivedObstaclesPositions);
					influenceKinematic(behaviour.getLinear(), behaviour.getAngular());
				}
			} else { // else it wanders
				BehaviourOutput behaviour = this.wanderBehaviour.runWander(getPosition(), getDirection(),
						getCurrentLinearSpeed(), getMaxLinearSpeed(), getCurrentAngularSpeed(), getMaxAngularSpeed(),
						perceivedObstaclesPositions);
				influenceKinematic(behaviour.getLinear(), behaviour.getAngular());
			}
			// return to normal icon
			if (this.cooldown > 0 && this.cooldown <= getBody().getAttackAnimationEnd()) {
				getBody().setCurrentIcon(getBody().getNormalIcon());
			}
			if (this.cooldown > 0) {
				this.cooldown--;
			}
		}
		return null;
	}

	/**
	 * returns the nearest Marines
	 * 
	 * @param perceivedMarinesPositions
	 * @return
	 */
	protected UnitBody getNearestMarine(List<UnitBody> perceivedMarinesPositions) {
		int rangMin = 0, rang = 0;
		double minDistance, distance;
		minDistance = Math.sqrt(Math.pow((perceivedMarinesPositions.get(0).getPosition().x - this.getPosition().x), 2)
				+ Math.pow((perceivedMarinesPositions.get(0).getPosition().y - this.getPosition().y), 2));
		for (UnitBody unitBody : perceivedMarinesPositions) {
			distance = Math.sqrt(Math.pow((unitBody.getPosition().x - this.getPosition().x), 2)
					+ Math.pow((unitBody.getPosition().y - this.getPosition().y), 2));
			if (distance < minDistance) {
				rangMin = rang;
				minDistance = distance;
			}
			rang++;
		}
		return perceivedMarinesPositions.get(rangMin);
	}

	protected void attack(UnitBody unitBody) {
		// if the armor's prey is greatest than the shooter damage then nothing happens
		// else the prey damage equal to the damage minus armor
		if (this.getBody().getDamage() > unitBody.getArmor()) {
			unitBody.setHealth(unitBody.getHealth() - this.getBody().getDamage() + unitBody.getArmor());
		}
		getBody().setCurrentIcon(getBody().getAttackIcon());
		this.cooldown = getBody().getAttackCooldown();
	}

}
